<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2017/11/10
 * Time: 14:37
 */

namespace common\business\task;

use common\business\CommonHelper;
use common\business\DbHandler;
use common\consts\CommonResources;
use common\consts\Consts;
use common\helpers\MemoryLock;
use common\helpers\StringUtil;
use common\result\ActionResult;
use Yii;
use common\helpers\DateHelper;
use common\models\Task;
use yii\base\UserException;
use yii\base\Exception;
use yii\helpers\Json;

class TaskService
{
    /**
     * @var TaskRequestData
     */
    public $requestData;

    /**
     * @var Task
     */
    public $task;

    /**
     * @var TaskHandlerInterface
     */
    protected $taskHandler;

//    /**
//     * TaskService constructor.
//     * @param TaskRequestData $requestData
//     * @param Task $task
//     */
//    public function __construct(Task $task)
//    {
//        $this->task = $task;
//        $this->requestData = TaskRequestData::buildFromTask($task);
//        $this->taskHandler = TaskHelper::getTaskHandler($this->task);
//    }


    /**
     * @param $type string
     * @param $from_system string
     * @param int $is_synchronization
     * @param string $request_data
     * @param int $priority
     * @return ActionResult
     */
    public function createTask($type, $from_system, $is_synchronization = Consts::NO, $request_data = '', $priority = 1)
    {
        $action = Yii::t('app' , 'Create');
        $modelClass = Yii::t('app' , 'Task');

        if (!key_exists($type, Consts::TASK_TYPE_MAP()))
            return new ActionResult(false, CommonResources::ACTION_FAIL_MSG($action, $modelClass, 'Task Type not Found'));

        $task = new Task([
            'type' => $type,
            'priority' => $priority,
            'key' => uniqid($type.'_'),
            'from_system' => $from_system,
            'request_data' => $request_data,
            'status' => Consts::TASK_STATUS_OPEN,
            'is_synchronization' => $is_synchronization,
            'next_run_date' => DateHelper::getCurrentDateTime(-30),
        ]);

        if ($this->isDuplicatedTaskExist($task)){
            return new ActionResult(true, CommonResources::ACTION_SUCCESS_MSG_DETAIL($action, $modelClass, 'Duplicated Task Exist'), true);
        }

        $api = new TaskApiHandler();
        $isSuccess = $api->createTask($task);
        if (!$isSuccess){
            return new ActionResult(true, CommonResources::ACTION_FAIL_MSG($action, $modelClass, $api->getErrorsMessage()));
        }
        return new ActionResult(true, CommonResources::ACTION_SUCCESS_MSG($action, $modelClass));
    }

    /**
     * @param $task Task
     * @return bool
     */
    protected function isDuplicatedTaskExist($task){
        $duplicatedTaskInDb = Task::findOne([
            'status' => [Consts::TASK_STATUS_OPEN, Consts::TASK_STATUS_ERROR, Consts::TASK_STATUS_RUNNING],
            'type' => $task->type,
            'from_system' => $task->from_system,
            'request_data' => $task->request_data,
        ]);
        if (!$duplicatedTaskInDb) return false;
        return true;
    }

    /**
     * 通过复制，创建一个新的Task.
     * @param $isSync boolean 是否为同步Task
     * @return Task
     * @throws Exception
     */
    protected function cloneNewTask($isSync)
    {
        $task                     = new Task();
        $task->type               = $this->requestData->type;
        $task->key                = $this->requestData->key;
        $task->from_system        = $this->requestData->from_system;
        $task->request_data       = json_encode($this->requestData, JSON_UNESCAPED_UNICODE);
        $task->is_synchronization = $isSync;
        $task->status             = Consts::TASK_STATUS_OPEN;
        $task->create_at          = DateHelper::getCurrentDateTime();
        $task->priority           = $this->task->priority;

        if (!$task->save()) {
            throw  new Exception(json_encode($task->getErrors(), JSON_UNESCAPED_UNICODE));
        }
        return $task;
    }

    /**
     * 开始处理异步任务
     * @param $task Task
     * @throws Exception
     */
    public function startProcessing($task)
    {
        $affectedRow = Task::updateAll(
            [
                'status'      => Consts::TASK_STATUS_RUNNING,
                'retry_times' => $task->retry_times + 1,
                'started_at' => DateHelper::getCurrentDateTime(),
            ],
            [
                'id' => $task->id,
            ]
        );
        if ($affectedRow != 1) {
            $errMsg = CommonResources::ACTION_FAIL_MSG('Change Status to running', 'Task', 'Task not found');
            throw new Exception($errMsg);
        }
        $task->refresh();
    }

    /**
     * 验证输入的Json数据,是否符合标准格式.
     * @return TaskResponseData
     */
    protected function preValidateData()
    {
        //0:检查requestData是否为空
        if (!$this->requestData) {
            return new TaskResponseData(TaskResponseData::CODE_ERROR, CommonResources::ACTION_FAIL_MSG('Check', 'Params', 'Record Is Null'));
        }
        if (StringUtil::isNullOrWhiteSpace($this->requestData->type)) {
            return new TaskResponseData(TaskResponseData::CODE_ERROR, CommonResources::ACTION_FAIL_MSG('Check', 'Params', 'Type Is Null'));
        }
        if (StringUtil::isNullOrWhiteSpace($this->requestData->key)) {
            return new TaskResponseData(TaskResponseData::CODE_ERROR, CommonResources::ACTION_FAIL_MSG('Check', 'Params', 'Key Is Null'));
        }
        if (StringUtil::isNullOrWhiteSpace($this->requestData->from_system)) {
            return new TaskResponseData(TaskResponseData::CODE_ERROR, CommonResources::ACTION_FAIL_MSG('Check', 'Params', 'From System Is Null'));
        }
        return new TaskResponseData(TaskResponseData::CODE_SUCCESS, CommonResources::PARAMS_CHECK_PASSED());
    }

    /**
     * 处理核心业务.调用注入的业务处理方法.
     *
     * @param Task $task
     * @return TaskResponseData
     */
    protected function processCore($task)
    {
        return $this->taskHandler->process($task);
//        return call_user_func_array([$this->taskHandler, 'process'], [$task]);
    }

    /**
     * 处理任务
     * @param $task Task
     * @param $requestData TaskRequestData
     * @param $isSync boolean
     * @return TaskResponseData
     */
    public function processTask($task)
    {
//        return $task->task_is_synchronization == Consts::YES ? $this->processSync() : $this->processAsync();
//        1:开始处理
        try {
            $this->startProcessing($task);
        } catch (Exception $ex) {
            $errMsg = CommonResources::ACTION_FAIL_MSG('Execute', 'Task', CommonHelper::getExceptionMsg($ex));
            return new TaskResponseData(TaskResponseData::CODE_ERROR, $errMsg, $errMsg);
        }

//        $transaction = Yii::$app->db->beginTransaction();
        try {
            //1.1:根据Type获取Task Handler.
            $taskHandler = TaskHelper::getTaskHandler($task);
            if (!$taskHandler) {
                throw new Exception(sprintf("[%s]不能找到对应的Handler", $task->type));
            }

            if ($taskHandler->isNeedSuspend($task)) {
                $this->suspendTask($task);
                return new TaskResponseData(TaskResponseData::CODE_UN_FINISHED, CommonResources::ACTION_NAME_MODEL('Suspend', 'Task', $task->key));
            }

            //2:调用Handler 做业务处理
            $result = $taskHandler->process($task);
            //3:处理成功
            $this->processASyncSuccess($task, $result);
            return new TaskResponseData(TaskResponseData::CODE_SUCCESS, CommonResources::ACTION_NAME_MODEL_SUCCESS('Process', 'Task', $task->key));
//            $transaction->commit();
        } catch (Exception $ex) {
//            $transaction->rollBack();
            try {
                //4:处理失败
                $this->processASyncFail($task, $ex);

                $errMsg = CommonHelper::getExceptionMsg($ex, false);
                return new TaskResponseData(TaskResponseData::CODE_ERROR, CommonResources::ACTION_NAME_MODEL_FAIL('Process', 'Task', $task->key, $errMsg));

//                if (isset($taskHandler) && $taskHandler->isNeedNotifyException($task)) {
//                    $errMsg = CommonHelper::getExceptionMsg($ex, false);
//                    return new TaskResponseData(TaskResponseData::CODE_ERROR, CommonResources::ACTION_NAME_MODEL_FAIL('Process', 'Task', $task->task_key, $errMsg));
//                }
            } catch (Exception $ex) {
                $errMsg = CommonHelper::getExceptionMsg($ex, false);
                return new TaskResponseData(TaskResponseData::CODE_ERROR, CommonResources::ACTION_NAME_MODEL_FAIL('Process', 'Task', $task->key, $errMsg));
            }
        }
    }

    /**
     * 挂起任务
     * @param $task Task
     * @throws Exception
     */
    protected function suspendTask($task)
    {
        $task->suspend_times += 1;
        $task->retry_times   = 0;
        $task->next_run_date = DateHelper::getCurrentDateTime(30);
        $task->status        = Consts::TASK_STATUS_OPEN;
        if (!$task->save()) {
            throw new UserException(json_encode($task->getErrors(), JSON_UNESCAPED_UNICODE));
        }
    }

//    /**
//     * 返回幂等结果,有一些业务可能需要使用实时数据,不能使用缓存数据.
//     * @param $task Task
//     * @return TaskResponseData
//     */
//    private function getIdempotentResult($task)
//    {
//        try {
//            $result = new TaskResponseData();
//            $result->setAttributes(get_object_vars(json_decode($task->task_response_data)));
//            return $result;
//        }
//        catch (Exception $ex){
//            $errorMsg = $ex->getMessage() . $ex->getTraceAsString();
//            return new TaskResponseData(TaskResponseData::CODE_ERROR, $errorMsg);
//        }
//    }

    /**
     * 获取异步任务下次运行时间.
     *
     * @param $retryTimes integer
     *
     * @return string
     */
    protected function getNextRunDate($retryTimes)
    {
//        switch ($retryTimes) {
//            case 1:
//                return DateHelper::getCurrentDateTime(5);
//            case 2:
//                return DateHelper::getCurrentDateTime(30);
//            case 3:
//                return DateHelper::getCurrentDateTime(60);
//            case 4:
//                return DateHelper::getCurrentDateTime(120);
//            default:
//                return DateHelper::getCurrentDateTime(60 * 24 * 365 * 10);
//        }
        return DateHelper::getCurrentDateTime();
    }

    /**
     * 异步任务处理成功
     * @param $task Task
     * @param $result TaskResponseData
     * @throws Exception
     */
    protected function processASyncSuccess($task, $result)
    {
        //Code =2 ,任务处理中,还需要等待下次处理.
        if ($result->code == TaskResponseData::CODE_UN_FINISHED) {
            $task->status        = ($task->retry_times >= Consts::TASK_MAX_RETRY_TIMES ? Consts::TASK_STATUS_TERMINATED : Consts::TASK_STATUS_OPEN);
            $task->response_data = json_encode($result, JSON_UNESCAPED_UNICODE);
            $task->next_run_date = $this->getNextRunDate($task->retry_times);
        } else {
            if ($result->code == TaskResponseData::CODE_ERROR || $result->code == TaskResponseData::CODE_SUCCESS) {
                $task->response_data = json_encode($result, JSON_UNESCAPED_UNICODE);
                $task->status        = Consts::TASK_STATUS_FINISHED;
            } else {
                throw new Exception(CommonResources::CODE_NOT_SUPPORTED($result->code));
            }
        }

        $task->stopped_at = DateHelper::getCurrentDateTime();
        if (!$task->save()) {
            throw new Exception(CommonResources::ACTION_NAME_MODEL_FAIL('Save', 'Task', $task->key, Json::encode($task->getErrors())));
        }
    }

    /**
     * 异步任务处理失败
     * @param $task Task
     * @param $exception Exception
     * @throws Exception
     */
    protected function processASyncFail($task, $exception)
    {
        $task->refresh();
        $task->status = ($task->retry_times >= Consts::TASK_MAX_RETRY_TIMES ? Consts::TASK_STATUS_TERMINATED : Consts::TASK_STATUS_OPEN);
        $task->memo   = sprintf("====%s====", $task->retry_times).CommonHelper::getExceptionMsg($exception, false);
        $task->next_run_date = $this->getNextRunDate($task->retry_times);

        if (!$task->save()) {
            throw new Exception(CommonResources::ACTION_NAME_MODEL_FAIL('Save', 'Task', $task->key, Json::encode($task->getErrors())));
        }
    }
}